import asyncio
import time
from datetime import datetime
from typing import List

import math
import requests

from common.third_party.devmem import DevMemCache
from services.control.api import ControlApi
from services.ocb.api import OcbApi
from services.server_integration.schemas.alarms import Alarm
from services.server_integration.schemas.full_unit_configuration import FullUnitConfiguration
from services.services_module.schemas.configs import LteConfig, GsmConfig, Telemetry, ModuleType
from common.third_party.save_config import save_config, load_config, load_const
from settings import settings


class ServicesModule:
    __slots__ = [
        'power_api', 'ocb_api', 'lte_configs', 'gsm_configs', 'telemetry', 'module_type', 'mem', 'url', 'test',
        'is_filthy_sms_12', 'is_filthy_sms_34'
    ]

    BASE_ADDRESS = 0x43CA_0000
    FILTHY_SM_SERIAL_NUMBERS_12 = [
        '003-03 2303 2',
        '003-03 2303 4',
        '003-03 2303 5'
    ]

    FILTHY_SM_SERIAL_NUMBERS_34 = [
        '003-05 2303 2',
        '003-04 2303 1'
    ]

    def __init__(self) -> None:
        self.test = 0
        self.mem = DevMemCache(number_of_words=0x200)

        self.power_api = ControlApi()
        self.ocb_api = OcbApi()

        self.lte_configs: list = [
            load_config('sm/lte_config_0', LteConfig, LteConfig(amp_voltages=[3.8, 3.45, 1.75, 1.25])),
            load_config('sm/lte_config_1', LteConfig, LteConfig(amp_voltages=[3.8, 3.45, 1.75, 1.25])),
            load_config('sm/lte_config_2', LteConfig, LteConfig(amp_voltages=[3.8, 3.45, 1.6, 1.45])),
            load_config('sm/lte_config_3', LteConfig, LteConfig(amp_voltages=[3.8, 3.45, 1.6, 1.45])),
        ]

        self.gsm_configs: list = [
            load_config(f'sm/gsm_config_{i}', GsmConfig, GsmConfig())
            for i in range(settings.SERVICES_MODULE_NUMBER)
        ]

        self.module_type: list = [ModuleType.LTE for _ in range(settings.SERVICES_MODULE_NUMBER)]
        self.telemetry: list = [Telemetry(temperature=i) for i in range(settings.SERVICES_MODULE_NUMBER)]

        self.url = settings.SERVER_URL
        self.is_filthy_sms_12 = self.get_is_filthy_sms_12()
        self.is_filthy_sms_34 = self.get_is_filthy_sms_23()

    def write_ad3_gain(self, index: int, gain: int):
        save_config(f'sm/ad3_gain_{index}', int(gain))

    def lte_configure(self, index: int, config: LteConfig):
        data = self.create_lte_register_data(config.amp_voltages, config.sv_ref_voltage)
        self.write_lte_umts_register_data(index, data)
        save_config(f'sm/lte_config_{index}', config)

    def gsm_configure(self, index: int, config: GsmConfig):
        data = self.create_gps_register_data(config.bias_per_transistor, config.sv_ref_voltage)
        self.write_lte_umts_register_data(index, data)
        save_config(f'sm/gsm_config_{index}', config)

    async def poll(self) -> None:
        while True:
            await asyncio.sleep(3.0)
            try:
                if not settings.DEBUG:
                    ocb_current_28v = await self.ocb_api.get_all_ocb_current_28v()

                    for i in range(settings.SERVICES_MODULE_NUMBER):
                        if not await self.is_enabled(i):
                            self.telemetry[i] = Telemetry()
                            continue

                        power_calibration_val = self.get_config(i).power_calibration_value
                        data = self.read_service_module_data(i)
                        if data is None:
                            # self.telemetry[i] = Telemetry()
                            continue

                        model_type, rev = self.parce_model_type(data, self.module_type[i])
                        if self.is_filthy_sms_12 and i in [0, 1]:
                            telemetry = await self.parse_telemetry(data, i, power_calibration_val)
                        elif self.is_filthy_sms_34 and i in [2, 3]:
                            telemetry = await self.parse_telemetry(data, i, power_calibration_val)
                        elif model_type == ModuleType.LTE:
                            telemetry = await self.parse_telemetry(data, i, power_calibration_val)
                        elif model_type == ModuleType.GSM:
                            telemetry = self.parse_telemetry_gsm(data, i, power_calibration_val)
                        else:
                            telemetry = Telemetry()

                        if telemetry is None:
                            telemetry = self.telemetry[i]

                        telemetry.is_rev_1_2 = rev == 12
                        telemetry.is_rev_1_0 = rev == 10
                        if rev == 12:
                            save_config(f'sm/ad3_gain_{i}', 0)
                        elif rev == 10:
                            save_config(f'sm/ad3_gain_{i}', 10)

                        if telemetry is None:
                            self.telemetry[i].current_28v = ocb_current_28v.get(f'service_{i}_ocb')
                            self.telemetry[i].preamp_5v = ocb_current_28v.service_5v_ocb
                            continue

                        telemetry.current_28v = ocb_current_28v.get(f'service_{i}_ocb')
                        telemetry.preamp_5v = ocb_current_28v.service_5v_ocb
                        self.telemetry[i] = telemetry
                        self.module_type[i] = model_type


                        #self.lte_configure(i, self.lte_configs[i])

                        # if 'LTE' in data and data.count('#') >= 5:
                        #     telemetry, module_type = self.decode_lte_modem_temp_voltages(
                        #         encoded=data,
                        #         power_calibration_value=power_calibration_val,
                        #         sv_number=i,
                        #     )
                        #     telemetry.current_28v = ocb_current_28v.get(f'service_{i}_ocb')
                        #     telemetry.preamp_5v = ocb_current_28v.service_5v_ocb
                        #
                        # elif 'GSM' in data and data.count('#') >= 10:
                        #     telemetry, module_type = self.decode_gsm_temp_voltages(
                        #         encoded=data,
                        #         power_calibration_value=power_calibration_val,
                        #         sv_number=i
                        #     )
                        #     telemetry.current_28v = ocb_current_28v.get(f'service_{i}_ocb')
                        #     telemetry.preamp_5v = ocb_current_28v.service_5v_ocb
                        # else:
                        #     continue
                        #
                        # if telemetry is None:
                        #     continue
                        #
                        # self.telemetry[i] = telemetry
                        # if module_type.value == 'GSM':
                        #     self.gsm_configure(i, self.gsm_configs[i])
                        # else:
                        #     self.lte_configure(i, self.lte_configs[i])
                        #
                        # self.module_type[i] = module_type
                        # # count = [0 for _ in range(settings.SERVICES_MODULE_NUMBER)]
                        #
                        # # else:
                        # #     count[i] += 1
                        # #     if count[i] >= 4:
                        # #         self.module_type[i] = ModuleType.LTE
                        # #         self.telemetry[i] = Telemetry(temperature=i)

            except Exception as e:
                #print(f'Exception in poll: {e}')
                pass

    def get_config(self, index: int) -> LteConfig | GsmConfig:
        return (self.gsm_configs if self.module_type[index] == 'GSM' else self.lte_configs)[index]

    def write_lte_umts_register_data(self, index: int, data: List[int] = List) -> None:
        try:
            if not settings.DEBUG:
                mem = self.mem.get(self.BASE_ADDRESS - 0x0001_0000 * index)
                tx_ready_offset = 0x12c
                tx_buf_offset = 0x104
                word_size = 4

                for idx, b in enumerate(data):
                    mem.write(tx_buf_offset + idx * word_size, b)

                mem.write(tx_ready_offset, 1)
                mem.write(tx_ready_offset, 0)
            else:
                pass
        except Exception as e:
            #print(f'Exception in write_lte_umts_register_data: {e}')
            pass

    async def is_enabled(self, index: int) -> bool:
        return await self.power_api.get_control_state(f'service_{index}_state')

    def read_service_module_data(self, index: int) -> str | bool | None:
        try:
            if not settings.DEBUG:
                mem = self.mem.get(self.BASE_ADDRESS - 0x0001_0000 * index)
                if not mem.read(0x100)[0]:
                    return None

                rx_buf_size = 64
                word_size = 4
                rx_data = []

                for i in range(rx_buf_size):
                    if mem.read(i * word_size).get_value() > 127:
                        rx_data.append(127)
                    else:
                        rx_data.append(mem.read(i * word_size).get_value())
                return bytes(bytearray(rx_data)).decode('utf-8')
            return True
        except Exception as e:
            #print(f'Exception in read_service_module_data: {e}')
            pass

    def parce_model_type(self, data: str, module_type) -> tuple[ModuleType, bool]:
        prefix = data.split('#')[0]
        if prefix == 'GSM_REV1.5':
            return ModuleType.GSM, 15
        elif prefix == 'SM_REV1.0':
            return ModuleType.LTE, 10
        elif prefix == 'SM_REV1.1':
            return ModuleType.LTE, 11
        elif prefix == 'SM_REV1.2':
            return ModuleType.LTE, 12
        else:
            return module_type, 0

    async def parse_telemetry(self, data: str, index: int, power_calibration_value):
        def parse(data: str):
            def crc(data: str):
                crc = 0xFFFF

                for ch in data:
                    crc ^= ord(ch)
                    for _ in range(0, 8):
                        bcarry = crc & 0x0001
                        crc >>= 1
                        if bcarry:
                            crc ^= 0xA001

                return crc

            data = data.rstrip('\x00')
            begin, *msgs, end = data.split('#')

            target_crc = int(end.removeprefix('END'), base=16)
            rev = {
                'SM_REV1.0': 10,
                'SM_REV1.1': 11,
                'SM_REV1.2': 12
            }.get(begin, None)

            if rev is None:
                return None

            msg = data.removeprefix(begin).removesuffix(end)
            if rev == 10:
                msg = f'SM_REV1.0{msg}END'
            elif rev == 11:
                msg = f'SM_REV1.1{msg}END'
            elif rev == 12:
                msg = f'SM_REV1.2{msg}END'

            if crc(msg) != target_crc:
                return None

            return [int(float(word)) for word in msgs], rev

        try:
            raw_message = data
            data, rev = parse(data)

            if rev == 12:
                pcn = data[-3] - 50
                save_config(f'sm/pcn_{index}', pcn)

                control_reg_name = (
                    'service_0_ocb', 'service_1_ocb', 'service_2_ocb', 'service_3_ocb',
                )[index]
                await self.ocb_api.set_ocb_threshold(control_reg_name, 3.5)
            else:
                save_config(f'sm/pcn_{index}', 0)

            voltage_v28_raw = float(data[0]) * (4.096 / 2 ** 10)
            voltage_28v = voltage_v28_raw * 18.14893617021

            lna_v5_raw = float(data[1]) * (4.096 / 2 ** 10)
            lna_5v = lna_v5_raw * 3.941605839416

            reflected_pwr_raw = float(data[2]) * (4.096 / 2 ** 10)
            reflected_pwr = 0
            if index == 0:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[196, 38.9, 260, 30.8]) + power_calibration_value
            elif index == 1:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[434, 39.5, 511, 30.5]) + power_calibration_value
            elif index == 2:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[470, 30.9, 375, 39.2]) + power_calibration_value
            elif index == 3:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[460, 39.5, 576, 31.4]) + power_calibration_value
            temp_raw = float(data[3]) * (4.096 / 2 ** 10)
            temp = math.sqrt(((1.8639 - (temp_raw / 1)) / 0.00000388) + 2196200) + (-1481.96)

            return Telemetry(
                voltage_28v=voltage_28v,
                lna_5v=lna_5v,
                reflected_power=reflected_pwr,
                temperature=temp,
                raw_message=raw_message
            )
        except Exception as e:
            #print(f'parce_telemetry: {e}')
            pass

    def parse_telemetry_gsm(self, data: str, index: int, power_calibration_value):
        def parse(data: str):
            def crc(data: str):
                crc = 0xFFFF

                for ch in data:
                    crc ^= ord(ch)
                    for _ in range(0, 8):
                        bcarry = crc & 0x0001
                        crc >>= 1
                        if bcarry:
                            crc ^= 0xA001

                return crc

            data = data.rstrip('\x00')
            begin, *msgs, end = data.split('#')

            target_crc = int(end.removeprefix('END'), base=16)
            if begin not in ['GSM_REV1.5']:
                return None

            msg = data.removeprefix(begin).removesuffix(end)
            msg = f'GSM_REV1.5{msg}END'
            if crc(msg) != target_crc:
                return None

            return [int(word) for word in msgs]

        try:
            raw_message = data
            data = parse(data)

            voltage_v28_raw = float(data[0]) * (4.096 / 2 ** 10)
            voltage_28v = voltage_v28_raw * 18.14893617021

            lna_v5_raw = float(data[2]) * (4.096 / 2 ** 10)
            lna_5v = lna_v5_raw * 3.941605839416

            reflected_pwr_raw = float(data[4]) * (4.096 / 2 ** 10)
            reflected_pwr = 0
            if index == 0:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[196, 38.9, 260, 30.8]) + power_calibration_value
            elif index == 1:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[434, 39.5, 511, 30.5]) + power_calibration_value
            elif index == 2:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[470, 30.9, 375, 39.2]) + power_calibration_value
            elif index == 3:
                reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                              curve_data_in=[460, 39.5, 576, 31.4]) + power_calibration_value
            temp_raw = float(data[5]) * (4.096 / 2 ** 10)
            temp = math.sqrt(((1.8639 - (temp_raw / 1)) / 0.00000388) + 2196200) + (-1481.96)

            return Telemetry(
                voltage_28v=voltage_28v,
                lna_5v=lna_5v,
                reflected_power=reflected_pwr,
                temperature=temp,
                raw_message=raw_message
            )
        except Exception as e:
            #print(f'parce_telemetry: {e}')
            pass

    @staticmethod
    def return_data(reg0: int, reg1: int, reg2: int, reg3: int) -> list[int]:
        data = [
            20,
            (reg0 >> 8) & 0xff,
            reg0 & 0xff,
            (reg1 >> 8) & 0xff,
            reg1 & 0xff,
            (reg2 >> 8) & 0xff,
            reg2 & 0xff,
            (reg3 >> 8) & 0xff,
            reg3 & 0xff,
        ]
        crc = sum(data) & 0xff
        data.append(crc)
        return data

    @staticmethod
    def create_lte_register_data(dac_output_voltages, reference_voltage_level) -> list[int]:
        if reference_voltage_level == 0:
            return []

        dac_output_voltages = [float(x) for x in dac_output_voltages]
        reference_voltage_level = int(reference_voltage_level)

        regs = dac_output_voltages[:]
        for idx, v in enumerate(dac_output_voltages):
            regs[idx] = round(v / (reference_voltage_level / 255))

        reg0 = 0b0001_0000_0000_1111 | ((int(regs[1]) & 0xff) << 4)
        reg1 = 0b0101_0000_0000_0000 | ((int(regs[0]) & 0xff) << 4)
        reg2 = 0b0001_0000_0000_0000 | ((int(regs[2]) & 0xff) << 4)
        reg3 = 0b0101_0000_0000_0000 | ((int(regs[3]) & 0xff) << 4)

        data = ServicesModule.return_data(reg0, reg1, reg2, reg3)
        return data

    @staticmethod
    def create_gps_register_data(dac_output_voltages, reference_voltage_level) -> list[int]:
        if reference_voltage_level == 0:
            return []

        dac_output_voltages = [float(dac_output_voltages)]
        reference_voltage_level = int(reference_voltage_level)

        regs = dac_output_voltages[:]
        for idx, v in enumerate(dac_output_voltages):
            regs[idx] = round(v / (reference_voltage_level / 255))

        shunt_28v = True

        reg0 = 0b0001_0000_0000_0000 | ((int(regs[0]) & 0xff) << 4)
        reg1 = 0b0101_0000_0000_0000 | ((int(regs[0]) & 0xff) << 4)
        reg2 = 0b0000_0000_0000_0000
        reg3 = 0b0000_0000_0000_0000 | (int(shunt_28v) << 0)

        data = ServicesModule.return_data(reg0, reg1, reg2, reg3)
        return data

    def decode_gsm_temp_voltages(self, encoded, power_calibration_value, sv_number: int) -> tuple:
        arr = encoded.split('#')
        arr = arr[1:-1]

        voltage_28v_raw = float(arr[0]) * (4.096 / 2 ** 10)
        # current_28v_raw = float(arr[1]) * (4.096 / 2 ** 10)
        preamp_5v_raw = float(arr[2]) * (4.096 / 2 ** 10)
        lna_5v_raw = float(arr[3]) * (4.096 / 2 ** 10)
        direct_pwr_raw = float(arr[4]) * (4.096 / 2 ** 10)
        reflected_pwr_raw = float(arr[5]) * (4.096 / 2 ** 10)
        temp_raw = float(arr[6]) * (4.096 / 2 ** 10)
        bit_settings = arr[7]
        crc = arr[8]

        temp = math.sqrt(((1.8639 - (temp_raw / 1)) / 0.00000388) + 2196200) + (-1481.96)
        voltage_28v = voltage_28v_raw * 18.14893617021
        # current_28v = current_28v_raw * 18.14893617021
        preamp_5v = preamp_5v_raw * 3.941605839416
        lna_5v = lna_5v_raw * 3.941605839416

        direct_pwr = 0
        reflected_pwr = 0

        if sv_number == 0:
            direct_pwr = self.v_to_dbm(vin_measured=direct_pwr_raw,
                                       curve_data_in=[196, 38.9, 260, 30.8]) + power_calibration_value
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[196, 38.9, 260, 30.8]) + power_calibration_value
        elif sv_number == 1:
            direct_pwr = self.v_to_dbm(vin_measured=direct_pwr_raw,
                                       curve_data_in=[434, 39.5, 511, 30.5]) + power_calibration_value
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[434, 39.5, 511, 30.5]) + power_calibration_value
        elif sv_number == 2:
            direct_pwr = self.v_to_dbm(vin_measured=direct_pwr_raw,
                                       curve_data_in=[470, 30.9, 375, 39.2]) + power_calibration_value
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[470, 30.9, 375, 39.2]) + power_calibration_value
        elif sv_number == 3:
            direct_pwr = self.v_to_dbm(vin_measured=direct_pwr_raw,
                                       curve_data_in=[460, 39.5, 576, 31.4]) + power_calibration_value
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[460, 39.5, 576, 31.4]) + power_calibration_value

        telemetry = Telemetry(temperature=temp, voltage_28v=voltage_28v,
                              preamp_5v=preamp_5v, lna_5v=lna_5v, direct_power=direct_pwr,
                              reflected_power=reflected_pwr, bit_settings=bit_settings, crc=crc,
                              raw_message=str(encoded))
        return telemetry, ModuleType.GSM

    def get_is_filthy_sms_12(self):
        config: FullUnitConfiguration | None = load_const(
            'full_unit_config',
            FullUnitConfiguration,
            None
        )

        if config is None:
            return False
        else:
            return config.unit_configuration.main_module.serial_number in self.FILTHY_SM_SERIAL_NUMBERS_12

    def get_is_filthy_sms_23(self):
        config: FullUnitConfiguration | None = load_const(
            'full_unit_config',
            FullUnitConfiguration,
            None
        )

        if config is None:
            return False
        else:
            return config.unit_configuration.main_module.serial_number in self.FILTHY_SM_SERIAL_NUMBERS_34

    def decode_lte_modem_temp_voltages(self, encoded, power_calibration_value, sv_number: int) -> tuple:
        arr = encoded.split('#')
        arr = arr[1:-1]

        voltage_v28_raw = float(arr[0].split('_')[1]) * (4.096 / 2 ** 10)
        lna_v5_raw = float(arr[1].split('_')[1]) * (4.096 / 2 ** 10)
        reflected_pwr_raw = float(arr[2].split('_')[1]) * (4.096 / 2 ** 10)

        # sv_amp_out_power = 38
        # if (reflected_pwr_raw < 1.05 and 38 <= sv_amp_out_power <= 41) \
        #         or (reflected_pwr_raw < 1.1 and 35 <= sv_amp_out_power < 38):
        if reflected_pwr_raw < 1.0:
            self.test =+ 1
            if self.test >= 3:
                # self.power_api.set_control_state(name=f'service_{sv_number}_state', state=False)
                self.lte_configs[sv_number] = LteConfig(amp_voltages=[0 for _ in range(settings.SERVICES_MODULE_NUMBER)])
                self.lte_configure(sv_number, LteConfig(amp_voltages=[0 for _ in range(settings.SERVICES_MODULE_NUMBER)]))
                # requests.post(self.url, json=self.alarm.dict())

        temp_raw = float(arr[3].split('_')[1]) * (4.096 / 2 ** 10)

        temp = math.sqrt(((1.8639 - (temp_raw / 1)) / 0.00000388) + 2196200) + (-1481.96)
        voltage_28v = voltage_v28_raw * 18.14893617021
        lna_5v = lna_v5_raw * 3.941605839416

        reflected_pwr = 0

        if sv_number == 0:
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[196, 38.9, 260, 30.8]) + power_calibration_value
        elif sv_number == 1:
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[434, 39.5, 511, 30.5]) + power_calibration_value
        elif sv_number == 2:
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[470, 30.9, 375, 39.2]) + power_calibration_value
        elif sv_number == 3:
            reflected_pwr = self.v_to_dbm(vin_measured=reflected_pwr_raw,
                                          curve_data_in=[460, 39.5, 576, 31.4]) + power_calibration_value

        telemetry = Telemetry(temperature=temp, voltage_28v=voltage_28v, lna_5v=lna_5v,
                              reflected_power=reflected_pwr, raw_message=str(encoded))
        return telemetry, ModuleType.LTE

    @staticmethod
    def v_to_dbm(curve_data_in, vin_measured) -> int:
        y1 = curve_data_in[0]
        x1 = curve_data_in[1]
        y2 = curve_data_in[2]
        x2 = curve_data_in[3]

        c = 4.096 / (2 ** 10)  # 4

        y1 *= c
        y2 *= c

        # mslop = abs(y1 - y2) / step_size
        b = ((y2 * x1) - (y1 * x2)) / (x1 - x2)
        k = (y1 - b) / x1
        # v_intercept = -b / k

        dbm = (vin_measured - b) / k

        return dbm
